﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Windows.Media.Imaging;

using System.Text;
using System.Windows.Media.Effects;

namespace GameEngine
{
    public enum AnimationMode
    {
        repeat,  
        oneTime,
        reverse
    }

    /// <remarks>
    ///     Represents the animation for tiles/objects. Provides the methods to control them. 
    /// </remarks>
    public class Spritesheet
    {

        /// <summary>
        ///     The sprites/frames of the animation.
        /// </summary>
        private WriteableBitmap[] _sprites;
        /// <summary>
        ///     First sprite/frame of the animation.
        /// </summary>
        private IntPoint _from;
        /// <summary>
        ///     Last sprite/frame.
        /// </summary>
        private IntPoint _to;
        /// <summary>
        ///     Size of individual sprites/frames.
        /// </summary>
        private IntSize _spriteSize;
        /// <summary>
        ///     Current sprite/frame of the animation.
        /// </summary>
        private int _currentSprite = 0;
        /// <summary>
        ///     Source file for the animation.
        /// </summary>
        private String _source;
        /// <summary>
        ///     Source file for the animation.
        /// </summary>
        private List<String> _sources;
        /// <summary>
        ///     Length of the animation in number of frames.
        /// </summary>
        private int _frameCount;
        
        /// <summary>
        ///     True if the spritesheet was created from one file.
        /// </summary>
        private bool _oneFile = true;
        public bool OneFile 
        {
            get
            {
                return _oneFile;
            }
        }

        /// <summary>
        ///     True if the animation is loaded.
        /// </summary>
        private bool _ready = false;
        public bool Ready
        {
            get
            {
                return _ready;
            }
        }


        /// <summary>
        ///     Clones a spritesheet.
        /// </summary>
        /// <param name="old">Spritesheet to be cloned.</param>
        /// <returns>An exact copy of the old spritesheet.</returns>
        public static Spritesheet Clone(Spritesheet old)
        {
            Spritesheet temp = new Spritesheet();

            temp._spriteSize = old._spriteSize;
            temp._frameCount = old._frameCount;
            temp._oneFile = old._oneFile;

            if (old._oneFile)
            {
                temp._source = old._source;
                temp._from = old._from;
                temp._to = old._to;
            }
            else
            {
                temp._sources = new List<string>();
                foreach (string item in old._sources)
                    temp._sources.Add(item);
            }

            temp._sprites = new WriteableBitmap[old._sprites.Length];
            int i = 0;
            foreach (WriteableBitmap item in old._sprites)
            {
                temp._sprites[i] = item.Clone();
                i++;
            }

            temp._ready = old._ready;

            return temp;
        }


        /// <summary>
        ///     Constructor used only for cloning.
        /// </summary>
        private Spritesheet()
        { }


        /// <summary>
        ///     Creates a new spritesheet from the file specified. Than it can be attached to any Tile or AnimatedObject. 
        /// </summary>
        /// <param name="spriteSheet">Path to the spritesheet file.</param>
        /// <param name="spriteSize">Size of one sprite(the rectangular area it covers). </param>
        /// <param name="frameCount">Number of frames the animation uses.</param>
        /// <param name="from">The upper left corner of the first sprite to load(the rectangular area it covers)in number of sprites(tiles)-not pixels.</param>
        /// <param name="to">The lower right corner of the last sprite to load(the rectangular area it covers)in number of sprites(tiles)-not pixels.</param>
        /// <param name="mode">Mode used for this animation.</param>
        public Spritesheet(String spriteSheet, IntSize spriteSize, int frameCount, IntPoint from, IntPoint to)
        {
            //saves each parameter so the object can be easily cloned
            this._source = spriteSheet;
            this._spriteSize = spriteSize;
            this._frameCount = frameCount;
            this._from = from;
            this._to = to;
            this._oneFile = true;

            this._sprites = new WriteableBitmap[frameCount];

            //creates a new image holder
            Image image = new Image();
            image.Height = spriteSize.Height;
            image.Width = spriteSize.Width;
            image.Stretch = Stretch.Uniform;
            //hides the image - we don't need to see it
            image.Visibility = Visibility.Collapsed;
            //and adds it to the visual tree so we can use the imageopened event
            (GameEngine.App.Current.RootVisual as MainPage).LayoutRoot.Children.Add(image);
            //sets the handler
            image.ImageOpened += new EventHandler<RoutedEventArgs>(LoadSpriteSheet);
            //set the image's source
            image.Source = new BitmapImage(new Uri(spriteSheet, UriKind.RelativeOrAbsolute));
        }


        /// <summary>
        ///      Creates a new spritesheet from the files specified. One file == one frame.
        ///      All of them have to be the same size. 
        ///      Than it can be attached to any Tile or AnimatedObject.    
        /// </summary>
        /// <param name="sprites">Array of paths to the spritesheets files.</param>
        /// <param name="spriteSize">Size of the sprite.</param>
        public Spritesheet(String[] sprites, IntSize spriteSize)
        {
            //saves each parameter so the object can be easily cloned
            this._sources = new List<String>();
            foreach (String item in sprites)
                this._sources.Add(item);
            this._spriteSize = spriteSize;
            this._frameCount = sprites.Length;
            this._oneFile = false;

            this._sprites = new WriteableBitmap[this._frameCount];
            for(int i=0;i<this._frameCount;i++)
            {
                this._sprites[i] = new WriteableBitmap(_spriteSize.Width, _spriteSize.Height);
            }

            //creates new image holders
            foreach (string item in this._sources)
            {
                Image image = new Image();
                image.Height = spriteSize.Height;
                image.Width = spriteSize.Width;
                image.Stretch = Stretch.Uniform;
                //hides the image - we don't need to see it
                image.Visibility = Visibility.Collapsed;
                //and adds it to the visual tree so we can use the imageopened event
                (GameEngine.App.Current.RootVisual as MainPage).LayoutRoot.Children.Add(image);
                //sets the handler
                image.ImageOpened += new EventHandler<RoutedEventArgs>(LoadSprites);
                //set the image's source
                image.Source = new BitmapImage(new Uri(item, UriKind.RelativeOrAbsolute));
            }
        }


        /// <summary>
        ///     Loads the sprite-frames from multiple files.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void LoadSprites(object sender, RoutedEventArgs e)
        {
            Image image = (sender as Image);
            String source =(image.Source as BitmapImage).UriSource.ToString();

            int index = _sources.IndexOf(source);
            this._sprites[index].Render(image, null);
            this._sprites[index].Invalidate();

            _ready = true;

            //removes the image from the visual tree-we do not need it anymore
            (GameEngine.App.Current.RootVisual as MainPage).LayoutRoot.Children.Remove(image);  
        }


        /// <summary>
        ///     Loads the sprites from the spritesheet.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void LoadSpriteSheet(object sender, RoutedEventArgs e)
        {
            Image image = (sender as Image);

            int i = 0;
            for (int y = this._from.Y; y < this._to.Y; y++)
            {
                for (int x = this._from.X; x < this._to.X; x++)
                {
                    TranslateTransform translation = new TranslateTransform();
                    translation.X = -(x * this._spriteSize.Width);
                    translation.Y = -(y * this._spriteSize.Height);

                    this._sprites[i] = new WriteableBitmap(_spriteSize.Width, _spriteSize.Height);
                    this._sprites[i].Render(image, translation);
                    this._sprites[i].Invalidate();
                    i++;
                }
            }
            _ready = true;

            //removes the image from the visual tree-we do not need it anymore
            (GameEngine.App.Current.RootVisual as MainPage).LayoutRoot.Children.Remove(image);
        }

        /// <summary>
        ///     Returns the string representation of this spritesheet.
        /// </summary>
        /// <returns></returns>
        public string StringRepresentation()
        {
            StringBuilder temp = new StringBuilder();

            if (this._oneFile)
            {
                //start mark 1
                temp.Append("?1" + Environment.NewLine);
                temp.Append(this._spriteSize.Width.ToString() + " " + this._spriteSize.Height.ToString() + Environment.NewLine);
                temp.Append(this._source + Environment.NewLine);
                temp.Append(this._frameCount + Environment.NewLine);
                temp.Append(this._from.X.ToString() + " " + this._from.Y.ToString() + Environment.NewLine);
                temp.Append(this._to.X.ToString() + " " + this._to.X.ToString() + Environment.NewLine);
            }
            else
            {
                //or start mark 2
                temp.Append("?2" + Environment.NewLine);
                temp.Append(this._spriteSize.Width.ToString() + " " + this._spriteSize.Height.ToString() + Environment.NewLine);
                foreach (string item in _sources)
                {
                    temp.Append(item + Environment.NewLine);
                }
            }

            //end mark
            temp.Append("?");

            return temp.ToString();

        }


        /// <summary>
        ///     Returns the first sprite in spritesheet.
        /// </summary>
        /// <returns></returns>
        public WriteableBitmap First()
        {
            return _sprites[0];
        }


        /// <summary>
        ///     Returns the next sprite in spritesheet.
        /// </summary>
        /// <returns></returns>
        public WriteableBitmap Next()
        {
            //in the end of animation
            if (_currentSprite == _sprites.Length)
            {
                _currentSprite = 0;
            }

            return _sprites[_currentSprite++];
        }


        /// <summary>
        ///     Returns the previous sprite in spritesheet.
        /// </summary>
        /// <returns></returns>
        public WriteableBitmap Previous()
        {
            //in the beginning of animation
            if (_currentSprite == -1)
            {
                _currentSprite = _sprites.Length - 1;
            }

            return _sprites[_currentSprite--];
        }


        /// <summary>
        ///     Sets the current sprite to 0.
        /// </summary>
        public void Reset()
        {
            this._currentSprite = 0;
        }
    }
}
